package edu.suda.cxtz.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import edu.suda.cxtz.entity.Dept;
import edu.suda.cxtz.repository.DeptRepository;
import edu.suda.cxtz.repository.RoleRepository;
import edu.suda.cxtz.repository.UserRepository;
import edu.suda.cxtz.entity.dto.DeptDto;
import edu.suda.cxtz.entity.dto.DeptQueryCriteria;
import edu.suda.cxtz.service.DeptService;
import edu.suda.cxtz.utils.*;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class DeptServiceImpl implements DeptService {
    private final DeptRepository deptRepository;
    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final DtoEntityUtil mapper;

    /**
     * 查询所有部门信息。
     *
     * @param criteria 部门查询条件
     * @param isQuery 是否进行查询过滤
     * @return 部门信息列表
     * @throws Exception 如果查询过程中出现异常
     */
    @Override
    public List<DeptDto> queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception {
//        System.out.println("DeptServiceImpl");
        List<Dept>  depts = deptRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder));
        return mapper.trans(depts, DeptDto.class);
    }

    @Override
    public PageResult<DeptDto> queryAll(DeptQueryCriteria criteria, Pageable pageable) throws Exception {
        Page<Dept> deptPage = deptRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
        List<DeptDto> pageResult = mapper.trans(deptPage, DeptDto.class);
        return PageUtil.toPage(pageResult, deptPage.getTotalElements());
    }

    @Override
    public List<Dept> queryAll() {
        return deptRepository.findAll();
    }

    /**
     * 根据ID查询部门信息。
     * 该方法通过缓存机制，如果部门信息已在缓存中，则直接从缓存中获取，避免了重复查询数据库。
     * 使用缓存的键值生成方式为 'id:' 加上部门ID，以确保缓存的唯一性和易于查找。
     *
     * @param id 部门ID，用于查询特定部门的信息。
     * @return 返回查询到的部门信息DTO（数据传输对象）。
     */
    @Override
    public DeptDto findById(Long id) {
        // 通过ID查询部门信息，如果不存在则返回一个新的Dept实例。
        Dept dept = deptRepository.findById(id).orElseGet(Dept::new);
        // 验证查询到的部门ID是否为空，防止查询到不存在的部门。
        // ValidationUtil.isNull(dept.getId(),"Dept","id",id);
        if (ObjectUtil.isNull(dept.getId())){
            String msg = "id=" + id + "部门不存在";
            throw new RuntimeException(msg);
        }

        // 将查询到的部门实体转换为DTO格式返回。
        return mapper.trans(dept, DeptDto.class);
    }


    @Override
    public List<Dept> findByPid(long pid) {
        return deptRepository.findByPid(pid);
    }

    @Override
    public Set<Dept> findByRoleId(Long id) {
        return deptRepository.findByRoleId(id);
    }

    /**
     * 创建部门信息。
     *
     * @param resources 部门资源对象，包含待创建部门的详细信息。
     * @Transactional 注解确保该方法在一个数据库事务中执行，如果发生异常则回滚。
     * @Override 表明该方法重写了父类或接口中的方法。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(Dept resources) {
        // 保存部门资源到数据库
        deptRepository.save(resources);

        // 初始化子部门数量为0，表示当前部门刚创建时没有子部门
        // 计算子节点数目
        resources.setSubCount(0);

        // 更新父节点中子节点数目
        updateSubCnt(resources.getPid());
    }


    /**
     * 更新部门信息。
     *
     * @param resources 部门资源对象，包含待更新的部门信息。
     * @throws RuntimeException 如果部门尝试将其父部门设置为自身，则抛出此异常。
     * @Transactional 注解确保此方法在一个数据库事务中执行，如果发生异常则回滚。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(Dept resources) {
        // 获取当前部门的旧父部门ID
        // 旧的部门
        Long oldPid = findById(resources.getId()).getPid();
        // 获取新父部门ID
        Long newPid = resources.getPid();
        // 验证是否尝试将父部门设置为自身，如果是，则抛出异常
        if(resources.getPid() != null && resources.getId().equals(resources.getPid())) {
            throw new RuntimeException("上级不能为自己");
        }
        // 根据ID查找部门，如果不存在则创建一个新的Dept实例
        Dept dept = deptRepository.findById(resources.getId()).orElseGet(Dept::new);
        // 验证部门ID是否存在，如果不存在，则抛出异常
        ValidationUtil.isNull(dept.getId(), "Dept", "id", resources.getId());
        // 更新部门信息
        resources.setId(dept.getId());
        deptRepository.save(resources);
        // 更新旧父部门和新父部门的子部门数量
        // 更新父节点中子节点数目
        updateSubCnt(oldPid);
        updateSubCnt(newPid);
    }


    /**
     * 删除部门信息。
     * 此方法通过遍历给定的部门数据传输对象集合，逐个删除每个部门。
     * 在删除每个部门之前，会先清除相关的缓存信息，然后更新该部门父级的子部门数量。
     * 使用@Transactional注解确保整个操作在数据库事务中完成，任何异常都将导致事务回滚。
     *
     * @param deptDtos 部门数据传输对象的集合，包含了需要被删除的部门信息。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Set<DeptDto> deptDtos) {
        for (DeptDto deptDto : deptDtos) {
            // 从数据库中删除指定的部门。
            deptRepository.deleteById(deptDto.getId());
            // 更新部门父级的子部门数量，维护部门结构的准确性。
            updateSubCnt(deptDto.getPid());
        }
    }

    /**
     * 获取需要删除的部门及其子部门的集合。
     * 该方法通过递归方式遍历所有子部门，确保在删除一个部门时，其下的所有子部门也被一并删除。
     *
     * @param menuList 部门列表，用于遍历查找需要删除的部门及其子部门。
     * @param deptDtos 已经收集到的需要删除的部门及其子部门的集合，以DeptDto的形式存储。
     * @return 返回一个包含所有需要删除的部门及其子部门的集合。
     */
    @Override
    public Set<DeptDto> getDeleteDepts(List<Dept> menuList, Set<DeptDto> deptDtos) {
        // 遍历菜单列表中的每个部门
        for (Dept dept : menuList) {
            // 将当前部门转换为DeptDto类型，并添加到deptDtos集合中
            deptDtos.add(mapper.trans(dept, DeptDto.class));
            // 根据当前部门的ID查找其下的子部门
            List<Dept> depts = deptRepository.findByPid(dept.getId());
            // 如果找到了子部门，并且子部门的数量不为0，则递归调用本方法处理子部门
            if(depts!=null && !depts.isEmpty()){
                getDeleteDepts(depts, deptDtos);
            }
        }
        // 返回收集到的所有需要删除的部门及其子部门的集合
        return deptDtos;
    }

    /**
     * 获取部门及其子部门的ID列表。
     * 通过递归方式，针对给定的部门列表，收集所有子部门的ID，包括自身ID。
     *
     * @param deptList 部门列表，用于遍历并收集子部门ID。
     * @return 包含给定部门及其所有子部门ID的列表。
     */
    @Override
    public List<Long> getDeptChildren(List<Dept> deptList) {
        // 初始化一个列表用于存储部门ID
        List<Long> list = new ArrayList<>();
        // 遍历部门列表
        deptList.forEach(dept -> {
                    // 检查部门是否非空且启用
                    if (dept != null && dept.getEnabled()) {
                        // 根据父ID查找子部门
                        List<Dept> depts = deptRepository.findByPid(dept.getId());
                        // 如果子部门不为空，则递归收集子部门的ID
                        if (!depts.isEmpty()) {
                            list.addAll(getDeptChildren(depts));
                        }
                        // 添加当前部门ID到列表中
                        list.add(dept.getId());
                    }
                }
        );
        // 返回包含所有部门及其子部门ID的列表
        return list;
    }


    /**
     * 获取指定部门及其所有上级部门的信息。
     *
     * @param deptDto 部门信息的数据传输对象，包含部门ID。
     * @param depts 用于存储查询到的部门信息的列表。
     * @return 包含指定部门及其所有上级部门的列表。
     */
    @Override
    public List<DeptDto> getSuperior(DeptDto deptDto, List<Dept> depts) {
        List<DeptDto> deptDtos = new ArrayList<>();
        // 如果部门父ID为空，说明查询的是根部门，需要查询所有父级为空的部门
        if(deptDto.getPid() == null){
            // 查询所有父级为空的部门，并添加到depts列表中
            depts.addAll(deptRepository.findByPidIsNull());
            // 将depts列表转换为DeptDto列表后返回
            for(Dept dept:depts){
                deptDtos.add(mapper.trans(dept, DeptDto.class));
            }
            return deptDtos;
        }
        // 查询指定父ID的部门，并添加到depts列表中
        depts.addAll(deptRepository.findByPid(deptDto.getPid()));
        // 递归调用获取指定部门的上级部门，直到找到根部门
        return getSuperior(findById(deptDto.getPid()), depts);
    }


    /**
     * 根据部门DTO列表构建部门树结构。
     *
     * @param deptDtos 部门数据传输对象的列表，包含所有部门信息。
     * @return 构建的部门树结构，以特定格式返回。
     */
    @Override
    public Object buildTree(List<DeptDto> deptDtos) {
        /* 存储根部门节点的集合 */
        Set<DeptDto> trees = new LinkedHashSet<>();
        /* 存储所有部门节点的集合 */
        Set<DeptDto> depts= new LinkedHashSet<>();
        /* 收集所有部门名称，用于后续判断 */
        List<String> deptNames = deptDtos.stream().map(DeptDto::getName).toList();

        /* 遍历每个部门DTO，构建树结构 */
        boolean isChild;
        for (DeptDto deptDTO : deptDtos) {
            isChild = false;
            /* 如果部门没有父部门，则认为是根部门，加入根部门集合 */
            if (deptDTO.getPid() == null) {
                trees.add(deptDTO);
            }
            /* 遍历所有部门DTO，查找当前部门的子部门，并构建父子关系 */
            for (DeptDto it : deptDtos) {
                if (it.getPid() != null && deptDTO.getId().equals(it.getPid())) {
                    isChild = true;
                    /* 如果当前部门还没有子部门列表，则初始化 */
                    if (deptDTO.getChildren() == null) {
                        deptDTO.setChildren(new ArrayList<>());
                    }
                    /* 将找到的子部门添加到当前部门的子部门列表中 */
                    deptDTO.getChildren().add(it);
                }
            }
            /* 如果当前部门是子部门，则加入到子部门集合中 */
            if(isChild) {
                depts.add(deptDTO);
            } else if(deptDTO.getPid() != null &&  !deptNames.contains(findById(deptDTO.getPid()).getName())) {
                /* 如果当前部门有父部门，但父部门不在已处理的部门名称列表中，则当前部门为叶子节点，加入到子部门集合中 */
                depts.add(deptDTO);
            }
        }

        /* 如果没有根部门，则将子部门集合作为根部门集合 */
        if (CollectionUtil.isEmpty(trees)) {
            trees = depts;
        }
        /* 构建返回结果，包含部门总数和部门树结构 */
        Map<String,Object> map = new HashMap<>(2);
        map.put("totalElements",deptDtos.size());
        map.put("content",CollectionUtil.isEmpty(trees)? deptDtos :trees);
        return map;
    }


    /**
     * 验证所传入的部门是否与用户或角色有关联。
     * 该方法旨在在删除或修改部门之前检查这些部门是否已被分配给用户或角色。
     * 如果发现任何关联，将抛出BadRequestException，指示需要先解除关联。
     *
     * @param deptDtos 部门数据集，包含待验证的部门信息。
     *                 每个DeptDto对象包含一个部门的ID，用于查询关联的用户或角色。
     * @throws RuntimeException 如果所选部门存在用户或角色关联，则抛出此异常。
     */
    @Override
    public void verification(Set<DeptDto> deptDtos) {
        // 通过流式处理deptDtos，提取所有部门ID，并收集为Set集合
        Set<Long> deptIds = deptDtos.stream().map(DeptDto::getId).collect(Collectors.toSet());

        // 检查是否有用户与所选部门关联
        if(userRepository.countByDepts(deptIds) > 0){
            throw new RuntimeException("所选部门存在用户关联，请解除后再试！");
        }

        // 检查是否有角色与所选部门关联
        if(roleRepository.countByDepts(deptIds) > 0){
            throw new RuntimeException("所选部门存在角色关联，请解除后再试！");
        }
    }


    /**
     * 更新部门的子部门数量。
     * 当部门ID不为空时，通过查询该部门的子部门数量，并将这个数量更新到该部门的记录中。
     * 这个方法反映了部门结构的动态变化，确保部门信息的统计是实时和准确的。
     *
     * @param deptId 部门ID，如果为null，则不进行更新操作。
     */
    private void updateSubCnt(Long deptId){
        // 检查部门ID是否为空，非空时才进行更新操作
        if(deptId != null){
            // 查询当前部门的子部门数量
            int count = deptRepository.countByPid(deptId);
            // 根据查询到的子部门数量，更新该部门的子部门数量字段
            deptRepository.updateSubCntById(count, deptId);
        }
    }


    /*
      去除列表中父部门ID重复的部门数据。
      通过遍历列表，对每个部门检查其父部门ID是否在列表中重复出现，如果不存在重复，则添加到新的列表中。
      这样做的目的是为了过滤掉那些作为子部门出现多次的部门，只保留其第一次出现时的数据。

      @param list 原始部门数据列表，可能包含重复的父部门ID。
      @return 过滤后的部门数据列表，保证每个部门的父部门ID在列表中唯一。
     */

    private List<DeptDto> deduplication(List<DeptDto> list) {
        // 初始化一个新的列表，用于存储过滤后的部门数据
        List<DeptDto> deptDtos = new ArrayList<>();
        // 遍历原始部门数据列表
        for (DeptDto deptDto : list) {
            // 默认设置标志位为true，表示当前部门的父部门ID在列表中不存在重复
            boolean flag = true;
            // 再次遍历列表，检查当前部门的父部门ID是否在列表中重复出现
            for (DeptDto dto : list) {
                // 如果找到重复的父部门ID，则将标志位设置为false，并跳出内层循环
                if (dto.getId().equals(deptDto.getPid())) {
                    flag = false;
                    break;
                }
            }
            // 如果标志位仍为true，说明当前部门的父部门ID在列表中唯一，将其添加到新的列表中
            if (flag) {
                deptDtos.add(deptDto);
            }
        }
        // 返回过滤后的部门数据列表
        return deptDtos;
    }
}
